

#import "UDMOctogris_XYView.h"
#import "CommonSettings.h"
//====================================================================================
typedef struct
{
	int i;
	float a;
} IndexedAngle;

int IndexedAngleCompare(const void *a, const void *b)
{
	IndexedAngle *ia = (IndexedAngle*)a;
	IndexedAngle *ib = (IndexedAngle*)b;
	return (ia->a < ib->a) ? -1 : ((ia->a > ib->a) ? 1 : 0);
}

//====================================================================================
static void ParameterListenerDispatcher (void *inRefCon, void *inObject, const AudioUnitParameter *inParameter, Float32 inValue)
{
    [(UDMOctogris_XYView *)inRefCon _parameterListener:inObject parameter:inParameter value:inValue];
}

//====================================================================================
@implementation UDMOctogris_XYView

- (id)initWithFrame:(NSRect)frame
{
    if ((self = [super initWithFrame:frame]))
	{
		mAU = nil;
		mParameterListener = nil;
		
		mNumberOfSources = 0;
		mNumberOfSpeakers = 0;
		
		mDummyPositions = nil;
		mSourcePositions = nil;
		mSourcePositionsDown = nil;
		mSourceAngularOrder = nil;
		mSourceDistances = nil;
		mOldSourceDistances = nil;
		
		mSpeakerPositions = nil;
		mSpeakerAttenuations = nil;
		
		mEnableJoystick = NO;
		mLinkMovement = NO;
		
		mMovementMode = 0;
		mShowGridLines = NO;
		
		mShowLevels = 0;
		mTakeAttenuation = NO;
		
		NSMutableParagraphStyle *ps = [[[NSMutableParagraphStyle alloc] init] autorelease];
		[ps setAlignment:NSCenterTextAlignment];
		mTextAttributes1 = [[NSDictionary dictionaryWithObjectsAndKeys:
										[NSColor whiteColor], NSForegroundColorAttributeName,
										ps, NSParagraphStyleAttributeName,
										nil] retain];
		mTextAttributes2 = [[NSDictionary dictionaryWithObjectsAndKeys:
										[NSColor blackColor], NSForegroundColorAttributeName,
										ps, NSParagraphStyleAttributeName,
										nil] retain];

    }
    return self;
}

- (void)setAU:(AudioUnit)inAU
{
	mAU = inAU;

	UInt32 dataSize = sizeof(mNumberOfSources);
	NSAssert (	AudioUnitGetProperty (mAU, kNumberOfSourcesPropertyID, kAudioUnitScope_Global, 0, &mNumberOfSources, &dataSize) == noErr,
				@"[UDMOctogris_XYView setAU:] AudioUnitGetProperty() 1");
		
	dataSize = sizeof(mNumberOfSpeakers);
	NSAssert (	AudioUnitGetProperty (mAU, kNumberOfSpeakersPropertyID, kAudioUnitScope_Global, 0, &mNumberOfSpeakers, &dataSize) == noErr,
				@"[UDMOctogris_XYView setAU:] AudioUnitGetProperty() 2");
				
	mDummyPositions = malloc(mNumberOfSources * sizeof(NSPoint));
	mSourcePositions = malloc(mNumberOfSources * sizeof(NSPoint));
	mSourcePositionsDown = malloc(mNumberOfSources * sizeof(NSPoint));
	mSourceAngularOrder = malloc(mNumberOfSources * sizeof(float));
	mSourceDistances = malloc(mNumberOfSources * sizeof(float));
	mOldSourceDistances = malloc(mNumberOfSources * sizeof(float));
	
	mSpeakerPositions = malloc(mNumberOfSpeakers * sizeof(NSPoint));
	mSpeakerAttenuations = malloc(mNumberOfSpeakers * sizeof(float));
	mSpeakerMutes = malloc(mNumberOfSpeakers * sizeof(BOOL));
	
	[self _addListeners];
	[self _synchronizeUIWithParameterValues];
}

- (void) dealloc
{
	[self _removeListeners];

	if (mDummyPositions) free(mDummyPositions);
	if (mSourcePositions) free(mSourcePositions);
	if (mSourcePositionsDown) free(mSourcePositionsDown);
	if (mSourceAngularOrder) free(mSourceAngularOrder);
	if (mSourceDistances) free(mSourceDistances);
	if (mOldSourceDistances) free(mOldSourceDistances);
	
	if (mSpeakerPositions) free(mSpeakerPositions);
	if (mSpeakerAttenuations) free(mSpeakerAttenuations);
	if (mSpeakerMutes) free(mSpeakerMutes);
	
	[mTextAttributes1 release];
	[mTextAttributes2 release];
	
	[mJoystickButtons release];
	[mJoysticks release];
	[super dealloc];
}

//====================================================================================
- (BOOL) isFlipped
{
	return NO;
}

- (BOOL) isOpaque
{
	return YES;
}

- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent
{
	return YES;
}

//====================================================================================
- (void)_addListeners
{
	NSAssert (	AUListenerCreate(	ParameterListenerDispatcher, self, 
                                    //CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 0.100, // 100 ms
									nil, nil, 0,
                                    &mParameterListener	) == noErr,
                @"[UDMOctogris_XYView _addListeners] AUListenerCreate()");
	
	int start = kConstantParameters;
	int end = start + mNumberOfSources * kParamsPerSource + mNumberOfSpeakers * kParamsPerSpeakers;
    for (int i = start; i < end; i++)
	{
			AudioUnitParameter parameter = { mAU, i, kAudioUnitScope_Global, 0 };
			NSAssert (	AUListenerAddParameter (mParameterListener, NULL, &parameter) == noErr,
						@"[UDMOctogris_XYView _addListeners] AUListenerAddParameter()");
	}
}

- (void)_removeListeners
{
    int start = kConstantParameters;
	int end = start + mNumberOfSources * kParamsPerSource + mNumberOfSpeakers * kParamsPerSpeakers;
    for (int i = start; i < end; i++)
	{
		AudioUnitParameter parameter = { mAU, i, kAudioUnitScope_Global, 0 };
        NSAssert (	AUListenerRemoveParameter(mParameterListener, NULL, &parameter) == noErr,
                    @"[UDMOctogris_XYView _removeListeners] AUListenerRemoveParameter()");
    }
    
	NSAssert (	AUListenerDispose(mParameterListener) == noErr,
                @"[UDMOctogris_XYView _removeListeners] AUListenerDispose()");
}

- (void)_synchronizeUIWithParameterValues
{
	Float32 value;    
    int start = kConstantParameters;
	int end = start + mNumberOfSources * kParamsPerSource + mNumberOfSpeakers * kParamsPerSpeakers;
    for (int i = start; i < end; i++)
	{
		AudioUnitParameter parameter = { mAU, i, kAudioUnitScope_Global, 0 };
		
        // only has global parameters
        NSAssert (	AudioUnitGetParameter(mAU, i, kAudioUnitScope_Global, 0, &value) == noErr,
                    @"[UDMOctogris_XYView synchronizeUIWithParameterValues] (x.1)");
        NSAssert (	AUParameterSet (mParameterListener, self, &parameter, value, 0) == noErr,
                    @"[UDMOctogris_XYView synchronizeUIWithParameterValues] (x.2)");
        NSAssert (	AUParameterListenerNotify (mParameterListener, self, &parameter) == noErr,
                    @"[UDMOctogris_XYView synchronizeUIWithParameterValues] (x.3)");
    }
}

//====================================================================================
- (void)_parameterListener:(void *)inObject parameter:(const AudioUnitParameter *)inParameter value:(Float32)inValue
{
    //inObject ignored in this case
	int param = inParameter->mParameterID;
    //NSLog(@"_parameterListener %i -> %f", param, inValue);
	
	[self setNeedsDisplay:YES];
	
	//This function is not for the constant params
	if (param < kConstantParameters)
	{
		return;
	}
	
	//Updating source parameters. Here is where I insert the code for "mousedrag"-style movement mode.
	param -= kConstantParameters;
	if (param < mNumberOfSources * kParamsPerSource)
	{
		int t = param % kParamsPerSource;
		int s = param / kParamsPerSource;
		switch(t)
		{
			case kSourceX: 
				mSourcePositions[s].x = inValue;
				//If this is for the first source and we are linking movement or distance, do the linked calcs
				if (s == 0)
				{
					if(mLinkMovement == YES)
					{
						[self continueLinkedMove];
					}
				}
				break;
				
			case kSourceY: 
				mSourcePositions[s].y = inValue;
				//If this is for the first source and we are linking movement or distance, do the linked calcs
				if (s == 0)
				{
					if(mLinkMovement == YES)
					{
						[self continueLinkedMove];
					}
				}
				break;
				
			case kSourceD:
				if(inValue != mSourceDistances[s])
				{
					[self changeDist:s inValue:inValue];
					//If this is for the first source and we are linking movement or distance, do the linked calcs
					if (s == 0)
					{
						if(mLinkDistance == YES)
						{
							for (int i = 1; i < mNumberOfSources; i++)
							{
								// for every other source, link this up
								[self changeDist:i inValue:inValue];
							}
						}
					}
				}
				break;
		}
		return;
	}
	
	param -= mNumberOfSources * kParamsPerSource;
	if (param < mNumberOfSpeakers * kParamsPerSpeakers)
	{
		int t = param % kParamsPerSpeakers;
		int s = param / kParamsPerSpeakers;
		switch(t)
		{
			case kSpeakerX: mSpeakerPositions[s].x = inValue; break;
			case kSpeakerY: mSpeakerPositions[s].y = inValue; break;
			case kSpeakerA: mSpeakerAttenuations[s] = inValue; break;
			case kSpeakerM: mSpeakerMutes[s] = (inValue > 0.5); break;
		}
		return;
	}

	// should never get here
	NSAssert(false, @"[UDMOctogris_XYView _parameterListener]");
}

//====================================================================================
- (void) notifyEventType:(AudioUnitEventType)type forParameterID:(AudioUnitParameterID)parameterID
{
	AudioUnitEvent myEvent;
	myEvent.mArgument.mParameter.mAudioUnit = mAU;
	myEvent.mArgument.mParameter.mParameterID = parameterID;
	myEvent.mArgument.mParameter.mScope = kAudioUnitScope_Global;
	myEvent.mArgument.mParameter.mElement = 0;
	myEvent.mEventType = type;
	AUEventListenerNotify(NULL, NULL, &myEvent);
}

//====================================================================================


- (void)changeDist:(int)s
		   inValue:(float)value
{
	mSourceDistances[s] = value;
	AudioUnitParameterID param = ParamForSourceD(s);
	
	NSAssert (	AudioUnitSetParameter(mAU, param, kAudioUnitScope_Global, 0, value, 0) == noErr,
			  @"[UDMOctogris_XYView sourceDistanceChanged] (x.1)");
	
	[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:param];
}

//====================================================================================
- (void)drawRect:(NSRect)rect
{
	rect = [self bounds];
	
	[[NSColor blackColor] set];
	[NSBezierPath fillRect:rect];
	
	// - - - - - - - - - - - -
	// draw the grid
	// - - - - - - - - - - - -
	if (mShowGridLines)
	{
		[[NSColor colorWithCalibratedRed:0 green:0 blue:0.3 alpha:1] set];
		const int gridCount = 8;
		for (int i = 1; i < gridCount; i++)
		{
			[NSBezierPath strokeLineFromPoint:NSMakePoint(rect.size.width * i / gridCount, 0) toPoint:NSMakePoint(rect.size.width * i / gridCount, rect.size.height)];
			[NSBezierPath strokeLineFromPoint:NSMakePoint(0, rect.size.height * i / gridCount) toPoint:NSMakePoint(rect.size.width, rect.size.height * i / gridCount)];
		}
	}
	
	const float padSize = rect.size.width;
	const float padOffset = 0;

	// - - - - - - - - - - - -
	// draw translucid circles
	// - - - - - - - - - - - -
	for (int i = 0; i < mNumberOfSources; i++)
	{
		float dist = mSourceDistances[i];
		float radius = 10, diameter = radius * 2;
		float x = mSourcePositions[i].x * (padSize - diameter) + (padOffset + radius);
		float y = padSize - (mSourcePositions[i].y * (padSize - diameter) + (padOffset + radius));
		
		float hue = (float)i / mNumberOfSources + 0.577251;
		if (hue > 1) hue -= 1;
				
		if (mSourceDistances[i] > 0.1)
		{
			radius = ((padSize - diameter) * sqrt(2)) / dist - radius;
			if (radius < 1) radius = 1;
			if (radius > 200) radius = 200;
			diameter = radius * 2;
			
			NSRect rect = NSMakeRect(x - radius, y - radius, diameter, diameter);
			NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:rect];
			[[NSColor colorWithCalibratedHue:hue saturation:1 brightness:1 alpha:0.1] set];
			[path fill];
			[[NSColor colorWithCalibratedHue:hue saturation:1 brightness:1 alpha:0.5] set];
			[path stroke];
		}
	}
	
	// - - - - - - - - - - - -
	// draw speakers
	// - - - - - - - - - - - -
	for (int i = 0; i < mNumberOfSpeakers; i++)
	{
		const float radius = 10, diameter = radius * 2;
		float x = mSpeakerPositions[i].x * (padSize - diameter) + (padOffset + radius);
		float y = padSize - (mSpeakerPositions[i].y * (padSize - diameter) + (padOffset + radius));
		
		NSRect rect = NSMakeRect(x - radius, y - radius, diameter, diameter);
		NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:rect];
		
		
		float allLevel = 0;
		
		{
			BOOL all = (mShowLevels - 1) >= mNumberOfSources;
			const float adj_factor = 1 / sqrtf(2);
			
			{
				float level = 0;
				NSPoint p = mSpeakerPositions[i];
				float a = dbToLinear(mSpeakerAttenuations[i]);
				BOOL m = mSpeakerMutes[i];
				
				if (!m)
					for (int j = 0; j < mNumberOfSources; j++)
					{
						NSPoint s = mSourcePositions[j];
						
						float dx = s.x - p.x;
						float dy = s.y - p.y;
						float d = sqrtf(dx*dx + dy*dy);
						float da = d * adj_factor * mSourceDistances[j];
						//if (da > 1) da = 0; else da = 1 - da;
						//New level calculations:
						if (da > 1) da = 1;
						if (da < 0.1) da = 0.1;
						da = -log10(da);
						
						if (mTakeAttenuation)
							da *= a;
						
						if (j == mShowLevels - 1 || all) level += da;
						allLevel += da;
					}
				
				if (mShowLevels > 0)
				{
					float dbLevel;
					if (level <= 1e-50) dbLevel = -99999;
					else dbLevel = linearToDb(level);
					
					NSString *ls;
					if (dbLevel <= -99999) { unichar c[2] = { '-', 0x221E }; ls = [NSString stringWithCharacters:c length:2]; }
					else ls = [NSString stringWithFormat:@"%.1f", dbLevel];

					NSRect rect2 = rect;
					rect2.origin.y -= 15;
					rect2.origin.x -= 10;
					rect2.size.width += 20;
					
					rect2.origin.x += 1; rect2.origin.y -= 1;
					[ls drawInRect:rect2 withAttributes:mTextAttributes2];
					rect2.origin.x -= 1; rect2.origin.y += 1;
					[ls drawInRect:rect2 withAttributes:mTextAttributes1];
				}
			}
		}
		
		
		[[NSColor colorWithCalibratedHue:2.f/3.f saturation:allLevel brightness:0.5 alpha:1] set];
		[path fill];
		
		[[NSColor whiteColor] set];
		[path stroke];
		
		NSString *s = [NSString stringWithFormat:@"%i", i+1];
		rect.origin.x += 1; rect.origin.y -= 1;
		[s drawInRect:rect withAttributes:mTextAttributes2];
		rect.origin.x -= 1; rect.origin.y += 1;
		[s drawInRect:rect withAttributes:mTextAttributes1];
	}
	
	// - - - - - - - - - - - -
	// draw sources
	// - - - - - - - - - - - -
	for (int i = 0; i < mNumberOfSources; i++)
	{
		float radius = 10, diameter = radius * 2;
		float x = mSourcePositions[i].x * (padSize - diameter) + (padOffset + radius);
		float y = padSize - (mSourcePositions[i].y * (padSize - diameter) + (padOffset + radius));
		
		NSRect rect = NSMakeRect(x - radius, y - radius, diameter, diameter);
		NSBezierPath *path = [NSBezierPath bezierPathWithOvalInRect:rect];
		
		float hue = (float)i / mNumberOfSources + 0.577251;
		if (hue > 1) hue -= 1;
		[[NSColor colorWithCalibratedHue:hue saturation:1 brightness:1 alpha:1] set];
		[path fill];
		
		[[NSColor redColor] set];
		[path stroke];
		
		NSString *s = [NSString stringWithFormat:@"%i", i+1];
		rect.origin.x += 1; rect.origin.y -= 1;
		[s drawInRect:rect withAttributes:mTextAttributes2];
		rect.origin.x -= 1; rect.origin.y += 1;
		[s drawInRect:rect withAttributes:mTextAttributes1];
	}
	
}
//====================================================================================
- (void)mouseDown:(NSEvent *)theEvent
{
	mSelectionType = kNoSelection;
	
	NSPoint ml = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	if (![self mouse:ml inRect:[self bounds]]) { [super mouseDown:theEvent]; return; }
	
	const float padSize = [self bounds].size.width;
	const float padOffset = 0;
	const float radius = 10, diameter = radius * 2;
	
	for (int i = mNumberOfSources - 1; i >= 0; i--)
	{
		float x = mSourcePositions[i].x * (padSize - diameter) + (padOffset + radius);
		float y = padSize - (mSourcePositions[i].y * (padSize - diameter) + (padOffset + radius));
		float distance = (ml.x - x)*(ml.x - x) + (ml.y - y)*(ml.y - y);
		if (distance < radius*radius)
		{
			mSelectionType = kSelectedSource;
			mSelectedItem = i;
			[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(mSelectedItem)];
			if (mMovementMode != 0 && mNumberOfSources > 1 && mLinkMovement == TRUE)
			{
				for (int j = 0; j < mNumberOfSources; j++)
				{
					mSourcePositionsDown[j] = mSourcePositions[j];
					if (j == mSelectedItem) continue;
					
					[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(j)];
					[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(j)];
				}
				
				if	(	(mNumberOfSources == 2 && (mMovementMode == 6 || mMovementMode == 7))
					 ||	(mNumberOfSources >  2 && (mMovementMode == 3 || mMovementMode == 4))
					 )
				{
					// need to calculate angular order
					
					IndexedAngle ia[mNumberOfSources];
					
					for (int j = 0; j < mNumberOfSources; j++)
					{
						float cx = mSourcePositionsDown[j].x - 0.5;
						float cy = mSourcePositionsDown[j].y - 0.5;
						
						ia[j].i = j;
						ia[j].a = atan2f(cy, cx);
					}
					
					//printf("==============\nbefore sort:\n");
					//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
					
					qsort(ia, mNumberOfSources, sizeof(IndexedAngle), IndexedAngleCompare);
					
					//printf("after sort:\n");
					//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
					
					int b;
					for (b = 0; b < mNumberOfSources && ia[b].i != mSelectedItem; b++)
					/**/;
					
					if (b == mNumberOfSources)
					{
						printf("error!\n");
						b = 0;
					}
					
					//printf("mSelectedItem: %i base: %i step: %.3f\n", mSelectedItem+1, b+1, (M_PI * 2.) / mNumberOfSources);
					
					for (int j = 1; j < mNumberOfSources; j++)
					{
						int o = (b + j) % mNumberOfSources;
						o = ia[o].i;
						mSourceAngularOrder[o] = (M_PI * 2. * j) / mNumberOfSources;
					}
					
					//for (int j = 0; j < mNumberOfSources; j++)  printf("mSourceAngularOrder[%i] = %.3f\n", j+1, mSourceAngularOrder[j]);
				}
			}
			return;
		}
	}
	
	for (int i = mNumberOfSpeakers - 1; i >= 0; i--)
	{
		float x = mSpeakerPositions[i].x * (padSize - diameter) + (padOffset + radius);
		float y = padSize - (mSpeakerPositions[i].y * (padSize - diameter) + (padOffset + radius));
		float distance = (ml.x - x)*(ml.x - x) + (ml.y - y)*(ml.y - y);
		if (distance < radius*radius)
		{
			mSelectionType = kSelectedSpeaker;
			mSelectedItem = i;
			[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSpeakerX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSpeakerY(mSelectedItem)];
			return;
		}
	}
}

- (void)mouseDragged:(NSEvent *)theEvent
{
	NSPoint ml = [self convertPoint:[theEvent locationInWindow] fromView:nil];
	// if (![self mouse:ml inRect:[self bounds]]) { [super mouseDragged:theEvent]; return; }
	
	const float padSize = [self bounds].size.width;
	const float padOffset = 0;
	const float radius = 10, diameter = radius * 2;
	
	switch(mSelectionType)
	{
		case kNoSelection:
			return;
			
		case kSelectedSource:
		{
			float vx = (ml.x - (padOffset + radius)) / (padSize - diameter);
			float vy = ((padSize - ml.y) - (padOffset + radius)) / (padSize - diameter);
			if (vx < 0) vx = 0; else if (vx > 1) vx = 1;
			if (vy < 0) vy = 0; else if (vy > 1) vy = 1;
			mSourcePositions[mSelectedItem].x = vx;
			mSourcePositions[mSelectedItem].y = vy;
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(mSelectedItem), kAudioUnitScope_Global, 0, vx, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.1)");
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(mSelectedItem), kAudioUnitScope_Global, 0, vy, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.2)");
			
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(mSelectedItem)];
			
			if (mMovementMode != 0 && mNumberOfSources == 2 && mLinkMovement == TRUE)
			{
				int otherItem = 1 - mSelectedItem;
				float vxo = vx, vyo = vy;
				switch(mMovementMode)
				{
					case 1: // sym x
						vyo = 1 - vyo;
						break;
						
					case 2: // sym y
						vxo = 1 - vxo;
						break;
						
					case 3: // sym x/y
						vxo = 1 - vxo;
						vyo = 1 - vyo;
						break;
						
					case 4: // circular
					{
						float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
						float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						cx = mSourcePositions[mSelectedItem].x - 0.5;
						cy = mSourcePositions[mSelectedItem].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[otherItem].x - 0.5;
						cy = mSourcePositionsDown[otherItem].y - 0.5;
						
						float cro = sqrt(cx*cx + cy*cy);
						float cao = atan2f(cy, cx);
						
						float cro2 = cro + cr2 - cr;
						float cao2 = cao + ca2 - ca;
						
						vxo = cro2 * cosf(cao2) + 0.5;
						vyo = cro2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 5: // circular, fixed radius
					{
						float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
						float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
						
						//float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						cx = mSourcePositions[mSelectedItem].x - 0.5;
						cy = mSourcePositions[mSelectedItem].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[otherItem].x - 0.5;
						cy = mSourcePositionsDown[otherItem].y - 0.5;
						
						//float cro = sqrt(cx*cx + cy*cy);
						float cao = atan2f(cy, cx);
						
						//float cro2 = cro + cr2 - cr;
						float cao2 = cao + ca2 - ca;
						
						vxo = cr2 * cosf(cao2) + 0.5;
						vyo = cr2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 6: // circular, fixed angle
					{
						float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
						float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						
						cx = mSourcePositions[mSelectedItem].x - 0.5;
						cy = mSourcePositions[mSelectedItem].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[otherItem].x - 0.5;
						cy = mSourcePositionsDown[otherItem].y - 0.5;
						
						float cro = sqrt(cx*cx + cy*cy);
						float cro2 = cro + cr2 - cr;
						float cao2 = ca2 + mSourceAngularOrder[otherItem];
						
						vxo = cro2 * cosf(cao2) + 0.5;
						vyo = cro2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 7: // circular, fully fixed
					{
						float cx = mSourcePositions[mSelectedItem].x - 0.5;
						float cy = mSourcePositions[mSelectedItem].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						float cao2 = ca + mSourceAngularOrder[otherItem];
						
						vxo = cr * cosf(cao2) + 0.5;
						vyo = cr * sinf(cao2) + 0.5;
					}
						break;
						
					case 8: // delta lock
					{
						float oriDx = mSourcePositionsDown[otherItem].x - mSourcePositionsDown[mSelectedItem].x;
						float oriDy = mSourcePositionsDown[otherItem].y - mSourcePositionsDown[mSelectedItem].y;
						
						vxo += oriDx;
						vyo += oriDy;
					}
						break;
				}
				
				if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
				if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
				mSourcePositions[otherItem].x = vxo;
				mSourcePositions[otherItem].y = vyo;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(otherItem), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(otherItem), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(otherItem)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(otherItem)];
			}
			else if (mMovementMode != 0 && mNumberOfSources > 2 && mLinkMovement == TRUE)
			{
				for (int i = 0; i < mNumberOfSources; i++)
				{
					if (i == mSelectedItem) continue;
					
					float vxo = vx, vyo = vy;
					switch(mMovementMode)
					{
						case 1: // circular
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[i].x - 0.5;
							cy = mSourcePositionsDown[i].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 2: // circular, fixed radius
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							//float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[i].x - 0.5;
							cy = mSourcePositionsDown[i].y - 0.5;
							
							//float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							//float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cr2 * cosf(cao2) + 0.5;
							vyo = cr2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 3: // circular, fixed angle
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[i].x - 0.5;
							cy = mSourcePositionsDown[i].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cro2 = cro + cr2 - cr;
							float cao2 = ca2 + mSourceAngularOrder[i];
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 4: // circular, fully fixed
						{
							float cx = mSourcePositions[mSelectedItem].x - 0.5;
							float cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							float cao2 = ca + mSourceAngularOrder[i];
							
							vxo = cr * cosf(cao2) + 0.5;
							vyo = cr * sinf(cao2) + 0.5;
						}
							break;
							
						case 5: // delta lock
						{
							float oriDx = mSourcePositionsDown[i].x - mSourcePositionsDown[mSelectedItem].x;
							float oriDy = mSourcePositionsDown[i].y - mSourcePositionsDown[mSelectedItem].y;
							
							vxo += oriDx;
							vyo += oriDy;
						}
							break;
					}
					
					if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
					if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
					mSourcePositions[i].x = vxo;
					mSourcePositions[i].y = vyo;
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(i), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(i), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
					
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(i)];
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(i)];
				}
			}
		}
			break;
			
		case kSelectedSpeaker:
		{
			float vx = (ml.x - (padOffset + radius)) / (padSize - diameter);
			float vy = ((padSize - ml.y) - (padOffset + radius)) / (padSize - diameter);
			if (vx < 0) vx = 0; else if (vx > 1) vx = 1;
			if (vy < 0) vy = 0; else if (vy > 1) vy = 1;
			mSpeakerPositions[mSelectedItem].x = vx;
			mSpeakerPositions[mSelectedItem].y = vy;
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSpeakerX(mSelectedItem), kAudioUnitScope_Global, 0, vx, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSpeakerY(mSelectedItem), kAudioUnitScope_Global, 0, vy, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
			
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSpeakerX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSpeakerY(mSelectedItem)];
		}
			break;
	}
	
	[self setNeedsDisplay:YES];
}

- (void)mouseUp:(NSEvent *)theEvent
{
	unsigned int flags = [theEvent modifierFlags];
	
	if(flags & NSAlternateKeyMask)
	{
		switch(mSelectionType)
		{
			case kNoSelection:
				return;
				
			case kSelectedSource:
			{
				double anglePerSource = 2 * M_PI / mNumberOfSources;
				double offset, axisOffset;
				float vx, vy;
				
				axisOffset = anglePerSource / 2;
				
				if (mNumberOfSources == 1)
				{
					vx = 0.5;
					vy = 0.5;
				}
				else
					for (int i = 0; i < mNumberOfSources; i++)
					{
						if(i%2 == 0) //if this is the left channel subtract the offset
						{
							offset = M_PI / 2 - axisOffset;
						}
						else //right channel, add the offset and update axisOffset
						{
							offset = M_PI / 2 + axisOffset;
							axisOffset += anglePerSource;
						}
						
						if (i == mSelectedItem)
						{
							vx = 0.5 - cos(offset)*0.25;
							vy = 0.5 - sin(offset)*0.25;
						}
					}
				
				if (vx < 0) vx = 0; else if (vx > 1) vx = 1;
				if (vy < 0) vy = 0; else if (vy > 1) vy = 1;
				mSourcePositions[mSelectedItem].x = vx;
				mSourcePositions[mSelectedItem].y = vy;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(mSelectedItem), kAudioUnitScope_Global, 0, vx, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.1)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(mSelectedItem), kAudioUnitScope_Global, 0, vy, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.2)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(mSelectedItem)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(mSelectedItem)];

				if (mMovementMode != 0 && mNumberOfSources == 2 && mLinkMovement == TRUE)
				{
					int otherItem = 1 - mSelectedItem;
					float vxo = vx, vyo = vy;
					switch(mMovementMode)
					{
						case 1: // sym x
							vyo = 1 - vyo;
							break;
							
						case 2: // sym y
							vxo = 1 - vxo;
							break;
							
						case 3: // sym x/y
							vxo = 1 - vxo;
							vyo = 1 - vyo;
							break;
							
						case 4: // circular
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 5: // circular, fixed radius
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							//float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							//float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							//float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cr2 * cosf(cao2) + 0.5;
							vyo = cr2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 6: // circular, fixed angle
						{
							float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
							float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							
							cx = mSourcePositions[mSelectedItem].x - 0.5;
							cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cro2 = cro + cr2 - cr;
							float cao2 = ca2 + mSourceAngularOrder[otherItem];
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 7: // circular, fully fixed
						{
							float cx = mSourcePositions[mSelectedItem].x - 0.5;
							float cy = mSourcePositions[mSelectedItem].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							float cao2 = ca + mSourceAngularOrder[otherItem];
							
							vxo = cr * cosf(cao2) + 0.5;
							vyo = cr * sinf(cao2) + 0.5;
						}
							break;
							
						case 8: // delta lock
						{
							float oriDx = mSourcePositionsDown[otherItem].x - mSourcePositionsDown[mSelectedItem].x;
							float oriDy = mSourcePositionsDown[otherItem].y - mSourcePositionsDown[mSelectedItem].y;
							
							vxo += oriDx;
							vyo += oriDy;
						}
							break;
					}
					
					if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
					if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
					mSourcePositions[otherItem].x = vxo;
					mSourcePositions[otherItem].y = vyo;
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(otherItem), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(otherItem), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
					
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(otherItem)];
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(otherItem)];
				}
				else if (mMovementMode != 0 && mNumberOfSources > 2 && mLinkMovement == TRUE)
				{
					for (int i = 0; i < mNumberOfSources; i++)
					{
						if (i == mSelectedItem) continue;
						
						float vxo = vx, vyo = vy;
						switch(mMovementMode)
						{
							case 1: // circular
							{
								float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
								float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[mSelectedItem].x - 0.5;
								cy = mSourcePositions[mSelectedItem].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 2: // circular, fixed radius
							{
								float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
								float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
								
								//float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[mSelectedItem].x - 0.5;
								cy = mSourcePositions[mSelectedItem].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								//float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								//float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cr2 * cosf(cao2) + 0.5;
								vyo = cr2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 3: // circular, fixed angle
							{
								float cx = mSourcePositionsDown[mSelectedItem].x - 0.5;
								float cy = mSourcePositionsDown[mSelectedItem].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								
								cx = mSourcePositions[mSelectedItem].x - 0.5;
								cy = mSourcePositions[mSelectedItem].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cro2 = cro + cr2 - cr;
								float cao2 = ca2 + mSourceAngularOrder[i];
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 4: // circular, fully fixed
							{
								float cx = mSourcePositions[mSelectedItem].x - 0.5;
								float cy = mSourcePositions[mSelectedItem].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								float cao2 = ca + mSourceAngularOrder[i];
								
								vxo = cr * cosf(cao2) + 0.5;
								vyo = cr * sinf(cao2) + 0.5;
							}
								break;
								
							case 5: // delta lock
							{
								float oriDx = mSourcePositionsDown[i].x - mSourcePositionsDown[mSelectedItem].x;
								float oriDy = mSourcePositionsDown[i].y - mSourcePositionsDown[mSelectedItem].y;
								
								vxo += oriDx;
								vyo += oriDy;
							}
								break;
						}
						
						if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
						if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
						mSourcePositions[i].x = vxo;
						mSourcePositions[i].y = vyo;
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(i), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(i), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
						
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(i)];
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(i)];
					}
				}
			}
				break;
				
			case kSelectedSpeaker:
			{
				double anglePerSpeakers = 2 * M_PI / mNumberOfSpeakers;
				double offset, axisOffset;
				float vx, vy;
				
				axisOffset = anglePerSpeakers / 2;
				for (int i = 0; i < mNumberOfSpeakers; i++)
				{
					if(i%2 == 0) //if this is the left channel subtract the offset
					{
						offset = M_PI / 2 - axisOffset;
					}
					else //right channel, add the offset and update axisOffset
					{
						offset = M_PI / 2 + axisOffset;
						axisOffset += anglePerSpeakers;
					}
					
					if (i == mSelectedItem)
					{
						vx = 0.5 - cos(offset)*0.5;
						vy = 0.5 - sin(offset)*0.5;
					}
				}
				
				if (vx < 0) vx = 0; else if (vx > 1) vx = 1;
				if (vy < 0) vy = 0; else if (vy > 1) vy = 1;
				mSpeakerPositions[mSelectedItem].x = vx;
				mSpeakerPositions[mSelectedItem].y = vy;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSpeakerX(mSelectedItem), kAudioUnitScope_Global, 0, vx, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSpeakerY(mSelectedItem), kAudioUnitScope_Global, 0, vy, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSpeakerX(mSelectedItem)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSpeakerY(mSelectedItem)];
			}
				break;
		}
	}
	
	switch(mSelectionType)
	{
		case kNoSelection:
			break;
			
		case kSelectedSource:
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(mSelectedItem)];
			if (mMovementMode != 0 && mNumberOfSources > 1 && mLinkMovement == TRUE)
			{
				for (int i = 0; i < mNumberOfSources; i++)
				{
					if (i == mSelectedItem) continue;
					[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(i)];
					[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(i)];
				}
			}
			break;
			
		case kSelectedSpeaker:
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSpeakerX(mSelectedItem)];
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSpeakerY(mSelectedItem)];
			break;
	}
	
	mSelectionType = kNoSelection;
}

//====================================================================================
- (void)setShowGridLines:(BOOL)showGridLines
{
	mShowGridLines = showGridLines;
	[self setNeedsDisplay:YES];
}

//====================================================================================
- (void)setMovementMode:(int)m
{
	if(mLinkMovement == YES)
	{
		[self startLinkedMove:TRUE];
	}

	mMovementMode = m;
	//NSLog(@"mMovementMode: %i", mMovementMode);
}

- (void)setShowLevels:(UInt32)s
{
	mShowLevels = s;
	[self setNeedsDisplay:YES];
}

- (void)setTakeAttenuation:(BOOL)t
{
	mTakeAttenuation = t;
	[self setNeedsDisplay:YES];
}

//====================================================================================
- (void)setEnableJoystick:(BOOL)enableJoystick
{
	mEnableJoystick = enableJoystick;
	//recalibrate joystick
	maxXAxis=0;
	minXAxis=65535;
	maxYAxis=0;
	minYAxis=65535;
	[self setNeedsDisplay:YES];
}

- (void)setLinkMovement:(BOOL)linkMovement
{
	mLinkMovement = linkMovement;
	
	//Start or stop a linked movement
	if(linkMovement == YES)
		[self startLinkedMove:FALSE];
	else {
		[self endLinkedMove];
	}

	[self setNeedsDisplay:YES];
}

- (void)setLinkDistance:(BOOL)linkDistance
{
	mLinkDistance = linkDistance;
}

//Like mousedown but accessible by external controllers
- (void)startLinkedMove:(BOOL)duringMove
{	
	//THIS FUNCTIONALITY IS ALWAYS LINKED TO SOURCE 1, TO AVOID DEADLOCKS
	int masterSource = 0;

	if (mMovementMode != 0 && mNumberOfSources > 1 && mLinkMovement == TRUE)
	{
		for (int j = 0; j < mNumberOfSources; j++)
		{
//			float x = mSourcePositions[j].x;
//			float y = mSourcePositions[j].y;
			mSourcePositionsDown[j] = mSourcePositions[j];
			if (j == masterSource) continue;
		}
		if(duringMove == FALSE) //only do this at the start of a movement.
			for (int j = 0; j < mNumberOfSources; j++)
			{
				[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(j)];
				[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(j)];
			}
		
		if	(	(mNumberOfSources == 2 && (mMovementMode == 6 || mMovementMode == 7))
			 ||	(mNumberOfSources >  2 && (mMovementMode == 3 || mMovementMode == 4))
			 )
		{
			// need to calculate angular order
			
			IndexedAngle ia[mNumberOfSources];
			
			for (int j = 0; j < mNumberOfSources; j++)
			{
				float cx = mSourcePositionsDown[j].x - 0.5;
				float cy = mSourcePositionsDown[j].y - 0.5;
				
				ia[j].i = j;
				ia[j].a = atan2f(cy, cx);
			}
			
			//printf("==============\nbefore sort:\n");
			//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
			
			qsort(ia, mNumberOfSources, sizeof(IndexedAngle), IndexedAngleCompare);
			
			//printf("after sort:\n");
			//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
			
			int b;
			for (b = 0; b < mNumberOfSources && ia[b].i != masterSource; b++)
			/**/;
			
			if (b == mNumberOfSources)
			{
				printf("error!\n");
				b = 0;
			}
			
			//printf("masterSource: %i base: %i step: %.3f\n", masterSource+1, b+1, (M_PI * 2.) / mNumberOfSources);
			
			for (int j = 1; j < mNumberOfSources; j++)
			{
				int o = (b + j) % mNumberOfSources;
				o = ia[o].i;
				mSourceAngularOrder[o] = (M_PI * 2. * j) / mNumberOfSources;
			}
			
			//for (int j = 0; j < mNumberOfSources; j++)  printf("mSourceAngularOrder[%i] = %.3f\n", j+1, mSourceAngularOrder[j]);
		}
	}
	
	if(duringMove == FALSE)
	{
		[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(masterSource)];
		[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(masterSource)];
	}
}

- (void)endLinkedMove
{
	//THIS FUNCTIONALITY IS ALWAYS LINKED TO SOURCE 1, TO AVOID DEADLOCKS
	int masterSource = 0;
	
	[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(masterSource)];
	[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(masterSource)];
	if (mMovementMode != 0 && mNumberOfSources > 1)
	{
		for (int i = 0; i < mNumberOfSources; i++)
		{
			if (i == masterSource) continue;
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(i)];
			[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(i)];
		}
	}
}

- (void)continueLinkedMove
{
	int masterSource = 0;
	{
		float vx = mSourcePositions[masterSource].x;
		float vy = mSourcePositions[masterSource].y;
		
		if (mMovementMode != 0 && mNumberOfSources == 2 && mLinkMovement == TRUE)
		{
			int otherItem = 1 - masterSource;
			float vxo = vx, vyo = vy;
			switch(mMovementMode)
			{
				case 1: // sym x
					vyo = 1 - vyo;
					break;
					
				case 2: // sym y
					vxo = 1 - vxo;
					break;
					
				case 3: // sym x/y
					vxo = 1 - vxo;
					vyo = 1 - vyo;
					break;
					
				case 4: // circular
				{
					float cx = mSourcePositionsDown[masterSource].x - 0.5;
					float cy = mSourcePositionsDown[masterSource].y - 0.5;
					
					float cr = sqrt(cx*cx + cy*cy);
					float ca = atan2f(cy, cx);
					
					cx = mSourcePositions[masterSource].x - 0.5;
					cy = mSourcePositions[masterSource].y - 0.5;
					
					float cr2 = sqrt(cx*cx + cy*cy);
					float ca2 = atan2f(cy, cx);
					
					cx = mSourcePositionsDown[otherItem].x - 0.5;
					cy = mSourcePositionsDown[otherItem].y - 0.5;
					
					float cro = sqrt(cx*cx + cy*cy);
					float cao = atan2f(cy, cx);
					
					float cro2 = cro + cr2 - cr;
					float cao2 = cao + ca2 - ca;
					
					vxo = cro2 * cosf(cao2) + 0.5;
					vyo = cro2 * sinf(cao2) + 0.5;
				}
					break;
					
				case 5: // circular, fixed radius
				{
					float cx = mSourcePositionsDown[masterSource].x - 0.5;
					float cy = mSourcePositionsDown[masterSource].y - 0.5;
					
					//float cr = sqrt(cx*cx + cy*cy);
					float ca = atan2f(cy, cx);
					
					cx = mSourcePositions[masterSource].x - 0.5;
					cy = mSourcePositions[masterSource].y - 0.5;
					
					float cr2 = sqrt(cx*cx + cy*cy);
					float ca2 = atan2f(cy, cx);
					
					cx = mSourcePositionsDown[otherItem].x - 0.5;
					cy = mSourcePositionsDown[otherItem].y - 0.5;
					
					//float cro = sqrt(cx*cx + cy*cy);
					float cao = atan2f(cy, cx);
					
					//float cro2 = cro + cr2 - cr;
					float cao2 = cao + ca2 - ca;
					
					vxo = cr2 * cosf(cao2) + 0.5;
					vyo = cr2 * sinf(cao2) + 0.5;
				}
					break;
					
				case 6: // circular, fixed angle
				{
					float cx = mSourcePositionsDown[masterSource].x - 0.5;
					float cy = mSourcePositionsDown[masterSource].y - 0.5;
					
					float cr = sqrt(cx*cx + cy*cy);
					
					cx = mSourcePositions[masterSource].x - 0.5;
					cy = mSourcePositions[masterSource].y - 0.5;
					
					float cr2 = sqrt(cx*cx + cy*cy);
					float ca2 = atan2f(cy, cx);
					
					cx = mSourcePositionsDown[otherItem].x - 0.5;
					cy = mSourcePositionsDown[otherItem].y - 0.5;
					
					float cro = sqrt(cx*cx + cy*cy);
					float cro2 = cro + cr2 - cr;
					float cao2 = ca2 + mSourceAngularOrder[otherItem];
					
					vxo = cro2 * cosf(cao2) + 0.5;
					vyo = cro2 * sinf(cao2) + 0.5;
				}
					break;
					
				case 7: // circular, fully fixed
				{
					float cx = mSourcePositions[masterSource].x - 0.5;
					float cy = mSourcePositions[masterSource].y - 0.5;
					
					float cr = sqrt(cx*cx + cy*cy);
					float ca = atan2f(cy, cx);
					
					float cao2 = ca + mSourceAngularOrder[otherItem];
					
					vxo = cr * cosf(cao2) + 0.5;
					vyo = cr * sinf(cao2) + 0.5;
				}
					break;
					
				case 8: // delta lock
				{
					float oriDx = mSourcePositionsDown[otherItem].x - mSourcePositionsDown[masterSource].x;
					float oriDy = mSourcePositionsDown[otherItem].y - mSourcePositionsDown[masterSource].y;
					
					vxo += oriDx;
					vyo += oriDy;
				}
					break;
			}
			
			if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
			if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
			mSourcePositions[otherItem].x = vxo;
			mSourcePositions[otherItem].y = vyo;
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(otherItem), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
			
			NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(otherItem), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
					  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
			
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(otherItem)];
			[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(otherItem)];
		}
		else if (mMovementMode != 0 && mNumberOfSources > 2 && mLinkMovement == TRUE)
		{
			for (int i = 0; i < mNumberOfSources; i++)
			{
				if (i == masterSource) continue;
				
				float vxo = vx, vyo = vy;
				switch(mMovementMode)
				{
					case 1: // circular
					{
						float cx = mSourcePositionsDown[masterSource].x - 0.5;
						float cy = mSourcePositionsDown[masterSource].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						cx = mSourcePositions[masterSource].x - 0.5;
						cy = mSourcePositions[masterSource].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[i].x - 0.5;
						cy = mSourcePositionsDown[i].y - 0.5;
						
						float cro = sqrt(cx*cx + cy*cy);
						float cao = atan2f(cy, cx);
						
						float cro2 = cro + cr2 - cr;
						float cao2 = cao + ca2 - ca;
						
						vxo = cro2 * cosf(cao2) + 0.5;
						vyo = cro2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 2: // circular, fixed radius
					{
						float cx = mSourcePositionsDown[masterSource].x - 0.5;
						float cy = mSourcePositionsDown[masterSource].y - 0.5;
						
						//float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						cx = mSourcePositions[masterSource].x - 0.5;
						cy = mSourcePositions[masterSource].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[i].x - 0.5;
						cy = mSourcePositionsDown[i].y - 0.5;
						
						//float cro = sqrt(cx*cx + cy*cy);
						float cao = atan2f(cy, cx);
						
						//float cro2 = cro + cr2 - cr;
						float cao2 = cao + ca2 - ca;
						
						vxo = cr2 * cosf(cao2) + 0.5;
						vyo = cr2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 3: // circular, fixed angle
					{
						float cx = mSourcePositionsDown[masterSource].x - 0.5;
						float cy = mSourcePositionsDown[masterSource].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						
						cx = mSourcePositions[masterSource].x - 0.5;
						cy = mSourcePositions[masterSource].y - 0.5;
						
						float cr2 = sqrt(cx*cx + cy*cy);
						float ca2 = atan2f(cy, cx);
						
						cx = mSourcePositionsDown[i].x - 0.5;
						cy = mSourcePositionsDown[i].y - 0.5;
						
						float cro = sqrt(cx*cx + cy*cy);
						float cro2 = cro + cr2 - cr;
						float cao2 = ca2 + mSourceAngularOrder[i];
						
						vxo = cro2 * cosf(cao2) + 0.5;
						vyo = cro2 * sinf(cao2) + 0.5;
					}
						break;
						
					case 4: // circular, fully fixed
					{
						float cx = mSourcePositions[masterSource].x - 0.5;
						float cy = mSourcePositions[masterSource].y - 0.5;
						
						float cr = sqrt(cx*cx + cy*cy);
						float ca = atan2f(cy, cx);
						
						float cao2 = ca + mSourceAngularOrder[i];
						
						vxo = cr * cosf(cao2) + 0.5;
						vyo = cr * sinf(cao2) + 0.5;
					}
						break;
						
					case 5: // delta lock
					{
						float oriDx = mSourcePositionsDown[i].x - mSourcePositionsDown[masterSource].x;
						float oriDy = mSourcePositionsDown[i].y - mSourcePositionsDown[masterSource].y;
						
						vxo += oriDx;
						vyo += oriDy;
					}
						break;
				}
				
				if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
				if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
				mSourcePositions[i].x = vxo;
				mSourcePositions[i].y = vyo;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(i), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(i), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(i)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(i)];
			}
		}
	}
	
	[self setNeedsDisplay:YES];
}

//====\/\/\/JOYSTICK STUFF\/\/\/========
- (void) awakeFromNib;
{
    NSArray * joysticks = [DDHidJoystick allJoysticks];
	
    mJoystickButtons = [[NSMutableArray alloc] init];
    [joysticks makeObjectsPerformSelector: @selector(setDelegate:)
                               withObject: self];
    [self setJoysticks: joysticks];
    if ([mJoysticks count] > 0)
        [self setJoystickIndex: 0];
    else
        [self setJoystickIndex: NSNotFound];
}

//=========================================================== 
//  joysticks 
//=========================================================== 
- (NSArray *) joysticks
{
    return mJoysticks; 
}

- (NSArray *) joystickButtons;
{
    return mJoystickButtons;
}

- (void) setJoysticks: (NSArray *) theJoysticks
{
    if (mJoysticks != theJoysticks)
    {
        [mJoysticks release];
        mJoysticks = [theJoysticks retain];
    }
}

//=========================================================== 
//  joystickIndex 
//=========================================================== 
- (unsigned) joystickIndex
{
    return mJoystickIndex;
}

- (void) setJoystickIndex: (NSInteger) theJoystickIndex
{
    if (mCurrentJoystick != nil)
    {
        [mCurrentJoystick stopListening];
        mCurrentJoystick = nil;
    }
    mJoystickIndex = theJoystickIndex;
    [mJoysticksController setSelectionIndex: mJoystickIndex];
    if (mJoystickIndex != NSNotFound)
    {
        mCurrentJoystick = [mJoysticks objectAtIndex: mJoystickIndex];
        [mCurrentJoystick startListening];
        
        [self willChangeValueForKey: @"joystickButtons"];
        [mJoystickButtons removeAllObjects];
        NSArray * buttons = [mCurrentJoystick buttonElements];
        NSEnumerator * e = [buttons objectEnumerator];
        DDHidElement * element;
        while (element = [e nextObject])
        {
            ButtonState * state = [[ButtonState alloc] initWithName: [[element usage] usageName]];
            [state autorelease];
            [mJoystickButtons addObject: state];
        }
        [self didChangeValueForKey: @"joystickButtons"];
    }
}

- (int) xAxis;
{
    return mXAxis;
}

- (int) yAxis;
{
    return mYAxis;
}

- (void) ddhidJoystick: (DDHidJoystick *)  joystick
                 stick: (unsigned) stick
              xChanged: (int) value;
{
    [self willChangeValueForKey: @"xAxis"];
//    mXAxis = value + JOYSTICK_AXISOFFSET;
	if (value < minXAxis)
		minXAxis = value;
	if (value > maxXAxis)
		if (value == minXAxis)
			maxXAxis = value + 1;
		else
			maxXAxis = value;
		
		mXAxis = (float)(value - minXAxis) / (float)(maxXAxis - minXAxis);
    [self didChangeValueForKey: @"xAxis"];
	
	//is joystick input enabled?
	if(mEnableJoystick == YES)
	{
		for(int button = 0; button < mNumberOfSources; button++)
			//			if(buttonstates[button] == TRUE) //First check if this button is pressed
			if([[mJoystickButtons objectAtIndex: button] pressed] == TRUE)
			{
				//then set the current x value as the joystick x value for the given source (from XYView mousedragged code)
				float vx = mXAxis;
				if (vx < 0) vx = 0; 
				else if (vx > 1) vx = 1;
				float vy = mYAxis;
				if (vy < 0) vy = 0; 
				else if (vy > 1) vy = 1;
				mSourcePositions[button].x = vx;
				mSourcePositions[button].y = vy;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(button), kAudioUnitScope_Global, 0, vx, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.1)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(button), kAudioUnitScope_Global, 0, vy, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.2)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(button)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(button)];
				
				if (mMovementMode != 0 && mNumberOfSources == 2 && mLinkMovement == TRUE)
				{
					int otherItem = 1 - button;
					float vxo = vx, vyo = vy;
					switch(mMovementMode)
					{
						case 1: // sym x
							vyo = 1 - vyo;
							break;
							
						case 2: // sym y
							vxo = 1 - vxo;
							break;
							
						case 3: // sym x/y
							vxo = 1 - vxo;
							vyo = 1 - vyo;
							break;
							
						case 4: // circular
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 5: // circular, fixed radius
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							//float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							//float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							//float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cr2 * cosf(cao2) + 0.5;
							vyo = cr2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 6: // circular, fixed angle
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cro2 = cro + cr2 - cr;
							float cao2 = ca2 + mSourceAngularOrder[otherItem];
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 7: // circular, fully fixed
						{
							float cx = mSourcePositions[button].x - 0.5;
							float cy = mSourcePositions[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							float cao2 = ca + mSourceAngularOrder[otherItem];
							
							vxo = cr * cosf(cao2) + 0.5;
							vyo = cr * sinf(cao2) + 0.5;
						}
							break;
							
						case 8: // delta lock
						{
							float oriDx = mSourcePositionsDown[otherItem].x - mSourcePositionsDown[button].x;
							float oriDy = mSourcePositionsDown[otherItem].y - mSourcePositionsDown[button].y;
							
							vxo += oriDx;
							vyo += oriDy;
						}
							break;
					}
					
					if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
					if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
					mSourcePositions[otherItem].x = vxo;
					mSourcePositions[otherItem].y = vyo;
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(otherItem), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(otherItem), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
					
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(otherItem)];
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(otherItem)];
				}
				else if (mMovementMode != 0 && mNumberOfSources > 2 && mLinkMovement == TRUE)
				{
					for (int i = 0; i < mNumberOfSources; i++)
					{
						if (i == button) continue;
						
						float vxo = vx, vyo = vy;
						switch(mMovementMode)
						{
							case 1: // circular
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 2: // circular, fixed radius
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								//float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								//float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								//float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cr2 * cosf(cao2) + 0.5;
								vyo = cr2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 3: // circular, fixed angle
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cro2 = cro + cr2 - cr;
								float cao2 = ca2 + mSourceAngularOrder[i];
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 4: // circular, fully fixed
							{
								float cx = mSourcePositions[button].x - 0.5;
								float cy = mSourcePositions[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								float cao2 = ca + mSourceAngularOrder[i];
								
								vxo = cr * cosf(cao2) + 0.5;
								vyo = cr * sinf(cao2) + 0.5;
							}
								break;
								
							case 5: // delta lock
							{
								float oriDx = mSourcePositionsDown[i].x - mSourcePositionsDown[button].x;
								float oriDy = mSourcePositionsDown[i].y - mSourcePositionsDown[button].y;
								
								vxo += oriDx;
								vyo += oriDy;
							}
								break;
						}
						
						if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
						if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
						mSourcePositions[i].x = vxo;
						mSourcePositions[i].y = vyo;
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(i), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(i), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
						
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(i)];
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(i)];
					}
				}
			}
		[self setNeedsDisplay:YES];
	}
}

- (void) ddhidJoystick: (DDHidJoystick *)  joystick
                 stick: (unsigned) stick
              yChanged: (int) value;
{
    [self willChangeValueForKey: @"yAxis"];
//    mYAxis = value + JOYSTICK_AXISOFFSET;
	if (value < minYAxis)
		minYAxis = value;
	if (value > maxYAxis)
		if (value == minYAxis)
			maxYAxis = value + 1;
		else
			maxYAxis = value;
	
	mYAxis = (float)(value - minYAxis) / (float)(maxYAxis - minYAxis);
    [self didChangeValueForKey: @"yAxis"];
	
	//is joystick input enabled?
	if(mEnableJoystick == YES)
	{
		for(int button = 0; button < mNumberOfSources; button++)
			//			if(buttonstates[button] == TRUE) //First check if this button is pressed
			if([[mJoystickButtons objectAtIndex: button] pressed] == TRUE)
			{
				//then set the current x value as the joystick x value for the given source (from XYView mousedragged code)
				float vx = mXAxis;
				if (vx < 0) vx = 0; 
				else if (vx > 1) vx = 1;
				float vy = mYAxis;
				if (vy < 0) vy = 0; 
				else if (vy > 1) vy = 1;
				mSourcePositions[button].x = vx;
				mSourcePositions[button].y = vy;
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(button), kAudioUnitScope_Global, 0, vx, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.1)");
				
				NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(button), kAudioUnitScope_Global, 0, vy, 0) == noErr,
						  @"[UDMOctogris_CocoaView mouseDragged] (x.2)");
				
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(button)];
				[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(button)];
				
				if (mMovementMode != 0 && mNumberOfSources == 2 && mLinkMovement == TRUE)
				{
					int otherItem = 1 - button;
					float vxo = vx, vyo = vy;
					switch(mMovementMode)
					{
						case 1: // sym x
							vyo = 1 - vyo;
							break;
							
						case 2: // sym y
							vxo = 1 - vxo;
							break;
							
						case 3: // sym x/y
							vxo = 1 - vxo;
							vyo = 1 - vyo;
							break;
							
						case 4: // circular
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 5: // circular, fixed radius
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							//float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							//float cro = sqrt(cx*cx + cy*cy);
							float cao = atan2f(cy, cx);
							
							//float cro2 = cro + cr2 - cr;
							float cao2 = cao + ca2 - ca;
							
							vxo = cr2 * cosf(cao2) + 0.5;
							vyo = cr2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 6: // circular, fixed angle
						{
							float cx = mSourcePositionsDown[button].x - 0.5;
							float cy = mSourcePositionsDown[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							
							cx = mSourcePositions[button].x - 0.5;
							cy = mSourcePositions[button].y - 0.5;
							
							float cr2 = sqrt(cx*cx + cy*cy);
							float ca2 = atan2f(cy, cx);
							
							cx = mSourcePositionsDown[otherItem].x - 0.5;
							cy = mSourcePositionsDown[otherItem].y - 0.5;
							
							float cro = sqrt(cx*cx + cy*cy);
							float cro2 = cro + cr2 - cr;
							float cao2 = ca2 + mSourceAngularOrder[otherItem];
							
							vxo = cro2 * cosf(cao2) + 0.5;
							vyo = cro2 * sinf(cao2) + 0.5;
						}
							break;
							
						case 7: // circular, fully fixed
						{
							float cx = mSourcePositions[button].x - 0.5;
							float cy = mSourcePositions[button].y - 0.5;
							
							float cr = sqrt(cx*cx + cy*cy);
							float ca = atan2f(cy, cx);
							
							float cao2 = ca + mSourceAngularOrder[otherItem];
							
							vxo = cr * cosf(cao2) + 0.5;
							vyo = cr * sinf(cao2) + 0.5;
						}
							break;
							
						case 8: // delta lock
						{
							float oriDx = mSourcePositionsDown[otherItem].x - mSourcePositionsDown[button].x;
							float oriDy = mSourcePositionsDown[otherItem].y - mSourcePositionsDown[button].y;
							
							vxo += oriDx;
							vyo += oriDy;
						}
							break;
					}
					
					if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
					if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
					mSourcePositions[otherItem].x = vxo;
					mSourcePositions[otherItem].y = vyo;
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(otherItem), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
					
					NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(otherItem), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
							  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
					
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(otherItem)];
					[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(otherItem)];
				}
				else if (mMovementMode != 0 && mNumberOfSources > 2 && mLinkMovement == TRUE)
				{
					for (int i = 0; i < mNumberOfSources; i++)
					{
						if (i == button) continue;
						
						float vxo = vx, vyo = vy;
						switch(mMovementMode)
						{
							case 1: // circular
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 2: // circular, fixed radius
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								//float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								//float cro = sqrt(cx*cx + cy*cy);
								float cao = atan2f(cy, cx);
								
								//float cro2 = cro + cr2 - cr;
								float cao2 = cao + ca2 - ca;
								
								vxo = cr2 * cosf(cao2) + 0.5;
								vyo = cr2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 3: // circular, fixed angle
							{
								float cx = mSourcePositionsDown[button].x - 0.5;
								float cy = mSourcePositionsDown[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								
								cx = mSourcePositions[button].x - 0.5;
								cy = mSourcePositions[button].y - 0.5;
								
								float cr2 = sqrt(cx*cx + cy*cy);
								float ca2 = atan2f(cy, cx);
								
								cx = mSourcePositionsDown[i].x - 0.5;
								cy = mSourcePositionsDown[i].y - 0.5;
								
								float cro = sqrt(cx*cx + cy*cy);
								float cro2 = cro + cr2 - cr;
								float cao2 = ca2 + mSourceAngularOrder[i];
								
								vxo = cro2 * cosf(cao2) + 0.5;
								vyo = cro2 * sinf(cao2) + 0.5;
							}
								break;
								
							case 4: // circular, fully fixed
							{
								float cx = mSourcePositions[button].x - 0.5;
								float cy = mSourcePositions[button].y - 0.5;
								
								float cr = sqrt(cx*cx + cy*cy);
								float ca = atan2f(cy, cx);
								
								float cao2 = ca + mSourceAngularOrder[i];
								
								vxo = cr * cosf(cao2) + 0.5;
								vyo = cr * sinf(cao2) + 0.5;
							}
								break;
								
							case 5: // delta lock
							{
								float oriDx = mSourcePositionsDown[i].x - mSourcePositionsDown[button].x;
								float oriDy = mSourcePositionsDown[i].y - mSourcePositionsDown[button].y;
								
								vxo += oriDx;
								vyo += oriDy;
							}
								break;
						}
						
						if (vxo < 0) vxo = 0; else if (vxo > 1) vxo = 1;
						if (vyo < 0) vyo = 0; else if (vyo > 1) vyo = 1;
						mSourcePositions[i].x = vxo;
						mSourcePositions[i].y = vyo;
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceX(i), kAudioUnitScope_Global, 0, vxo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.3)");
						
						NSAssert (	AudioUnitSetParameter(mAU, ParamForSourceY(i), kAudioUnitScope_Global, 0, vyo, 0) == noErr,
								  @"[UDMOctogris_CocoaView mouseDragged] (x.4)");
						
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceX(i)];
						[self notifyEventType:kAudioUnitEvent_ParameterValueChange forParameterID:ParamForSourceY(i)];
					}
				}
			}
		[self setNeedsDisplay:YES];
	}
	
}

- (void) ddhidJoystick: (DDHidJoystick *) joystick
                 stick: (unsigned) stick
             otherAxis: (unsigned) otherAxis
          valueChanged: (int) value;
{
    // Somehow display values here
    NSLog(@"Stick: %d, other axis: %d, changed: %d", stick, otherAxis, value);
}

- (void) ddhidJoystick: (DDHidJoystick *) joystick
                 stick: (unsigned) stick
             povNumber: (unsigned) povNumber
          valueChanged: (int) value;
{
    // Somehow display values here
    NSLog(@"Stick: %d, POV number: %d, changed: %d", stick, povNumber, value);
}

- (void) ddhidJoystick: (DDHidJoystick *) joystick
            buttonDown: (unsigned) buttonNumber;
{
    ButtonState * state = [mJoystickButtons objectAtIndex: buttonNumber];
    [state setPressed: YES];
	//buttonstates[buttonNumber] = TRUE;
	
	//from XYView mousedown code:
	if((mEnableJoystick == YES) && (mNumberOfSources > buttonNumber))
	{
		//		mSelectionType = kSelectedSource;
		//		mSelectedItem = buttonNumber;
		[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(buttonNumber)];
		[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(buttonNumber)];
		if (mMovementMode != 0 && mNumberOfSources > 1 && mLinkMovement == TRUE)
		{
			for (int j = 0; j < mNumberOfSources; j++)
			{
				mSourcePositionsDown[j] = mSourcePositions[j];
				if (j == buttonNumber) continue;
				
				[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceX(j)];
				[self notifyEventType:kAudioUnitEvent_BeginParameterChangeGesture forParameterID:ParamForSourceY(j)];
			}
			
			if	(	(mNumberOfSources == 2 && (mMovementMode == 6 || mMovementMode == 7))
				 ||	(mNumberOfSources >  2 && (mMovementMode == 3 || mMovementMode == 4))
				 )
			{
				// need to calculate angular order
				
				IndexedAngle ia[mNumberOfSources];
				
				for (int j = 0; j < mNumberOfSources; j++)
				{
					float cx = mSourcePositionsDown[j].x - 0.5;
					float cy = mSourcePositionsDown[j].y - 0.5;
					
					ia[j].i = j;
					ia[j].a = atan2f(cy, cx);
				}
				
				//printf("==============\nbefore sort:\n");
				//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
				
				qsort(ia, mNumberOfSources, sizeof(IndexedAngle), IndexedAngleCompare);
				
				//printf("after sort:\n");
				//for (int j = 0; j < mNumberOfSources; j++) printf("ia[%i] = { %i, %.3f }\n", j+1, ia[j].i+1, ia[j].a);
				
				int b;
				for (b = 0; b < mNumberOfSources && ia[b].i != buttonNumber; b++)
				/**/;
				
				if (b == mNumberOfSources)
				{
					printf("error!\n");
					b = 0;
				}
				
				//printf("buttonNumber: %i base: %i step: %.3f\n", buttonNumber+1, b+1, (M_PI * 2.) / mNumberOfSources);
				
				for (int j = 1; j < mNumberOfSources; j++)
				{
					int o = (b + j) % mNumberOfSources;
					o = ia[o].i;
					mSourceAngularOrder[o] = (M_PI * 2. * j) / mNumberOfSources;
				}
				
				//for (int j = 0; j < mNumberOfSources; j++)  printf("mSourceAngularOrder[%i] = %.3f\n", j+1, mSourceAngularOrder[j]);
			}
		}
				
	}
}

- (void) ddhidJoystick: (DDHidJoystick *) joystick
              buttonUp: (unsigned) buttonNumber;
{
    ButtonState * state = [mJoystickButtons objectAtIndex: buttonNumber];
    [state setPressed: NO];
	//buttonstates[buttonNumber] = FALSE;
	
	if((mEnableJoystick == YES) && (mNumberOfSources > buttonNumber))
	{
		[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(buttonNumber)];
		[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(buttonNumber)];
		if (mMovementMode != 0 && mNumberOfSources > 1 && mLinkMovement == TRUE)
		{
			for (int i = 0; i < mNumberOfSources; i++)
			{
				if (i == buttonNumber) continue;
				[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceX(i)];
				[self notifyEventType:kAudioUnitEvent_EndParameterChangeGesture forParameterID:ParamForSourceY(i)];
			}
		}
	}
}
//=========^^^JOYSTICK STUFF^^^=========


@end
